package de.suur.jarmi.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.suur.jarmi.common.Compressor;
import de.suur.jarmi.common.JarmiUtil;
import de.suur.jarmi.common.Request;
import de.suur.jarmi.common.Response;
import de.suur.jarmi.common.Serializer;

public class RequestHandler {

	private static final transient Logger log = LoggerFactory
			.getLogger(RequestHandler.class);

	private Compressor compressor;
	private Serializer serializer;
	private Map<Class<?>, Object> services;

	public RequestHandler() {
		services = new HashMap<Class<?>, Object>();
	}

	public <T> void addService(Class<T> clazz, T service) {
		services.put(clazz, service);
	}

	public byte[] compress(byte[] responseData) {
		if (compressor != null) {
			return compressor.compress(responseData);
		} else {
			return responseData;
		}
	}

	public Compressor getCompressor() {
		return compressor;
	}

	public Serializer getSerializer() {
		return serializer;
	}

	@SuppressWarnings("unchecked")
	public <T> T getService(Class<T> clazz) {
		return (T) services.get(clazz);
	}

	public Response handle(InputStream inputStream) {
		try {
			log.trace("Incoming request");
			byte[] intro = new byte[JarmiUtil.JARMI_BYTES.length];
			inputStream.read(intro);
			String jarmi = new String(intro);
			if (!jarmi.equals(JarmiUtil.JARMI)) {
				log.trace("Wrong intro: " + jarmi);
				return null;
			}

			byte[] resultSize = new byte[4];
			inputStream.read(resultSize);
			int size = JarmiUtil.byteArrayToInt(resultSize);
			log.debug("Handle request size: {}", size);
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			for (int i = 0; i < size; i++) {
				bos.write(inputStream.read());
			}

			byte[] requestData = bos.toByteArray();

			if (compressor != null) {
				requestData = compressor.decompress(requestData);
			}

			Request request = serializer.deserialize(requestData);

			if (request != null) {
				Class<?> serviceClass = request.getServiceClass();
				String methodName = request.getMethodName();
				Serializable[] params = request.getParams();
				Class<?>[] types = request.getTypes();

				Object service = getService(serviceClass);
				Class<?> cls = service.getClass();
				Response reply;

				try {
					Method method = cls.getMethod(methodName, types);
					reply = new Response((Serializable) method.invoke(service,
							(Object[]) params));
				} catch (Exception e) {
					reply = new Response(e);
					e.printStackTrace();
				}

				return reply;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public byte[] serialize(Response response) {
		return serializer.serialize(response);
	}

	public void setCompressor(Compressor compressor) {
		this.compressor = compressor;
	}

	public void setSerializer(Serializer serializer) {
		this.serializer = serializer;
	}

	public void writeResponse(OutputStream outputStream, byte[] responseData) {
		if (responseData == null) {
			return;
		}
		try {
			log.debug("Writing response length: {}", responseData.length);
			byte[] responseSize = JarmiUtil.intToByteArray(responseData.length);
			outputStream.write(responseSize);
			outputStream.write(responseData);
			outputStream.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
